package ic.doc.co2nudge.server;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Calendar;
import java.util.SortedMap;
import java.util.TreeMap;

import ic.doc.co2nudge.client.utils.Utils;
import ic.doc.co2nudge.services.DataService;
import ic.doc.co2nudge.shared.GraphType;
import ic.doc.co2nudge.shared.model.DataType;
import ic.doc.co2nudge.shared.model.ForecastData;
import ic.doc.co2nudge.shared.model.TransportType;
import ic.doc.co2nudge.shared.model.Trip;
import ic.doc.co2nudge.shared.model.TripOption;
import ic.doc.co2nudge.shared.model.User;
import ic.doc.co2nudge.shared.model.UserData;
import ic.doc.co2nudge.shared.Constants;

import com.google.gwt.i18n.shared.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;


public class DataServiceImpl extends RemoteServiceServlet
	implements DataService{

	private Objectify objService;
	
	private List<TransportType> 	transportTypes;
	private Double 					costsAverageValue;
	private Integer 				range=12;
	
	private static final long serialVersionUID = 3511909049754357265L;
	
	public DataServiceImpl(){
		ObjectifyService.register(TransportType.class);
		ObjectifyService.register(Trip.class);
		ObjectifyService.register(TripOption.class);
		ObjectifyService.register(User.class);
		objService=ObjectifyService.begin();
	}
	
	@Override
	public List<TransportType> getTransportTypes(){
		return this.transportTypes;
	}

	private void loadTransportTypes(){
		
		List<TransportType> transportTypes=
			new ArrayList<TransportType>();
		
		Query<TransportType> queryTransportTypes=objService
			.query(TransportType.class);
		
		if (queryTransportTypes!=null)
			for (TransportType t: queryTransportTypes)
				transportTypes.add(t);
		
		this.transportTypes=transportTypes;
	}
	
	@Override
	public HashMap<String,List<? extends DataType>> loadDictionaryData(){
		
		loadTransportTypes();
	
		HashMap<String, List<? extends DataType>> dictionary=
			new HashMap<String, List<? extends DataType>>();
		
		dictionary.put(TransportType.class.getName(), transportTypes);
		
		return dictionary;
	}

	/**
	 * Data entities storing functions
	 * */	
	@Override
	public void storeTransportTypes(List<TransportType> types){
		if ((types!=null)&&(types.size()>0))
			objService.put(types);	
	}
	
	public Boolean storeDictionary(List<TransportType> transportTypes){

		Boolean isStored=false;
		
		if ((transportTypes==null)){
			
			isStored=false;
		}
			
//		Object keyBudgetTypes=objService.put(budgetTypes);
//		Object keyNumOfPeopleTypes=objService.put(numOfPeopleTypes);
		Object keyTransportTypes=objService.put(transportTypes);
//		Object keyTripTypes=objService.put(tripTypes);
		
//		isStored&=(Boolean)(keyBudgetTypes!=null)?true:false;
//		isStored&=(Boolean)(keyNumOfPeopleTypes!=null)?true:false;
		isStored&=(Boolean)(keyTransportTypes!=null)?true:false;
//		isStored&=(Boolean)(keyTripTypes!=null)?true:false;
		
		return (Boolean)isStored;
	}

	public Boolean storeTrip(Trip trip, User user){
		
		boolean isStored=false;

		if (trip==null)
			return isStored;
		
		Key<User> keyUser=new Key<User>(User.class, user.getId());
		trip.setUserKey(keyUser);
		Key<Trip> keyTrip=(Key<Trip>)objService.put(trip);
		
		if ((keyTrip!=null)&&(keyTrip.getId()!=-1))
			isStored=true;
		
		return isStored;
	}
	
	public Boolean storeTripOption(TripOption option){
		boolean isStored=false;
		
		Key<TripOption> keyTripOption=(option!=null)?
				(Key<TripOption>)objService.put(option):
					null;

		if ((keyTripOption!=null)&&(keyTripOption.getId()!=-1))
			isStored=true;
		
		return (Boolean)isStored;
	}
	
	public void storeUser(User user){
		objService.put(user);
	
	}
	
	public void storeUsers(List<User> users){
		for(User user: users)
			objService.put(user);
	}
	
	public List<Trip> getAllTrips(){
		
		List<Trip> trips=
			new ArrayList<Trip>();
		
		Query<Trip> queryTrips=
			objService.query(Trip.class)
				.order("date");
		
		trips=queryTrips.list();
		return trips;
	}
	
	private List<Trip> getAllTripsSortedByTransportType(User user) {
		
		List<Trip> trips=
			new ArrayList<Trip>();
		
		Query<Trip> queryTrips=
			objService.query(Trip.class)
				.filter( "user ", user);
				//.order("chosenCO2option.trType.name");
		
		trips=queryTrips.list();
		Collections.sort( trips, new Comparator<Trip>() {

			@Override
			public int compare( Trip t1, Trip t2) {
				
				return (t1.getChosenCO2Option().getTransportType()
						.getId()>t2.getChosenCO2Option()
								.getTransportType().getId())?1:0;
			}
		});
		
		return trips;
	}
	
	public List<Trip> getTripsByYear(Date date, User user){
		
		List<Trip> trips=
			new ArrayList<Trip>();
		
		Query<Trip> queryTrips=
			objService.query(Trip.class).filter("user", user)
					.order("date");
		
		if (date!=null)
			queryTrips=queryTrips.filter("date >= ", date);
		
		trips=queryTrips.list();
		return trips;
	}
	
	public User getUser(String username, String password) {
		
		Query<User> queryUser=objService.query(User.class)
			.filter("username", username);
		User user=queryUser.list().get(0);
		
		return (user.getPassword()
				.equals(password))?user:null;
	}
	
	public List<TripOption> generateTripOptions(Double distance) {
		
		List<TripOption> options=
			new ArrayList<TripOption>();

		return options;
	}
	
	public Double getTripDistance(String from, String to){
		final Double distance=0D;
	
		return distance;
	}

	public HashMap<String, Double> loadStatisticalData(GraphType graphType){
		
		 HashMap<String, Double> resultData=
			new  HashMap<String, Double>();
		
		switch(graphType){
		
			case TRANSPORT_TYPES:{
				
				for (TransportType t: getTransportTypes()){					
					Query<Trip> query=objService
					.query(Trip.class);
					
					int count=query.count();
					int trTypeNum=0;
					
					trTypeNum=query
						.filter("chosenCO2option.trType.name" , t.getName())
							.count();
					
					resultData.put(t.getName(), 
							(double)trTypeNum);
				}
			
			} break;
			case CO2_RATE:{
				
				Query<Trip> q=objService.query(Trip.class);
				q=q.order("date");
				
				SimpleDateFormat sdf=new SimpleDateFormat("MMM-yy");
				
				String strBaseDate="";
				double rateSum=0.0D;
				
				for (Trip trip: q){
					
					String strDate=sdf.format(trip.getDate());
					
					if (strBaseDate.equals(""))
						strBaseDate=strDate;
					else
						if (!strBaseDate.equals(strDate)) {
							resultData.put(strBaseDate, rateSum);
							strBaseDate=strDate;	
							rateSum=0.0;
						}
					rateSum+=trip.getChosenCO2Option().getRate();
				}
				
				if (!resultData.containsKey(strBaseDate))
					resultData.put(strBaseDate, rateSum);
				
			} break;
			case NUM_OF_TRIPS: {
				
			} break;
			case USERS_CO2_RATES:{
				
			} break;
		}
		
		return resultData;
	}

	private List<Double> getMinimalTripOptionsRates(List<Trip> trips){
		
		List<Double> rates=
			new ArrayList<Double>();
		
		SimpleDateFormat sdf=new SimpleDateFormat("MM");
		double sum=trips.get(0).getMinCO2Option().getRate();
		int month= Integer.parseInt( sdf.format( trips.get(0).getDate()));

		for ( int i=1; i<trips.size(); i++) {
			
			int monthOfTrip =Integer.parseInt(sdf.format(trips
					.get(i).getDate()));
			
			if ( month!=monthOfTrip) {
				
				if (rates.size()==0)
					rates.add( sum);
				else
					rates.add( rates.get(rates.size()-1) + sum);
				
				month=monthOfTrip;	
				sum=0.0;
			}
			sum+=trips.get(i).getMinCO2Option().getRate();
		}
		
		rates.add( rates.get(rates.size()-1) + sum);
		return rates;
	}
	
	private List<Double> getTripsOptionsRates(List<Trip> trips) {
		
		if ((trips==null)||(trips.size()==0))
				return null;
		
		List<Double> rates=new ArrayList<Double>();
		
		SimpleDateFormat sdf=new SimpleDateFormat("MM");
		double sum=trips.get(0).getChosenCO2Option().getRate();
		
		double tripsCostsSum= Constants.getConstants().getConstant(trips
				.get(0).getChosenCO2Option().getTransportType().getName());
		
		int month=Integer.parseInt(sdf.format( trips.get(0).getDate()));

		for ( int i=1; i<trips.size(); i++) {
			tripsCostsSum+=Constants.getConstants().getConstant(trips
					.get(i).getChosenCO2Option().getTransportType().getName());
			
			Integer monthOfTrip=Integer
				.parseInt(sdf.format(trips.get(i)
						.getDate()));
			
			if (month!=monthOfTrip) {
				if (rates.size()==0)
					rates.add( sum);
				else
					rates.add( rates.get( rates.size()-1) + sum);
				
				month=monthOfTrip;	
				sum=0.0;
			}
			sum+=trips.get(i).getChosenCO2Option().getRate();
		}
		rates.add(rates.get(rates.size()-1) + sum);
		
		return rates;
	}
	
	public ForecastData loadPredictionData(User user, Date date){
		
		List<Trip> trips=getTripsByYear(date, user);
		
		if ((trips==null)||(trips.size()==0))
			return null;
		
		ForecastData fData=new ForecastData();
		
		List<Double> tripsRates=
			getTripsOptionsRates(trips);
	
		List<Double> tripsMinRates=
			getMinimalTripOptionsRates(trips);
		
		List<Double> forecastedMinValues=
			new ArrayList<Double>();

		int forecastStartIndex=tripsRates.size();
		double nextValue=0D;
		
		for (int i=0; i<range-forecastStartIndex; i++) {
			nextValue=PredictionService.calcNextAverageValue(tripsRates);
			tripsRates.add( tripsRates.get(tripsRates.size()-1)
					+ nextValue);
		}
		
		for (int i=0; i<range-forecastStartIndex; i++) {
			nextValue=PredictionService.calcNextAverageValue(tripsMinRates);
			tripsMinRates.add( tripsMinRates.get(tripsMinRates.size()-1)
					+ nextValue);
		}
		
		double limitValue=calcGoalValue( tripsRates.get(tripsRates.size()-1), 
				tripsMinRates.get( tripsMinRates.size()-1));
	
		fData.setTripsRates( tripsRates);
		fData.setMinRates( tripsMinRates);
		fData.setForecastStartIndex(forecastStartIndex);
		fData.setLimitValue( limitValue);
		
		return fData;
	}
	
	private double calcGoalValue(double chosenOptionsSum, double minOptionsSum) {
		
		double difInPercentage=100 
			- (minOptionsSum/chosenOptionsSum)*100;
		double limitPercentage=(20*difInPercentage)/100;
		double limitValue= chosenOptionsSum 
			- (limitPercentage*chosenOptionsSum)/100;
		
		return limitValue;
	}
	
	public List<UserData> loadAnalysisData() {
		
		List<UserData> usersData=
			new ArrayList<UserData>();
		
		HashMap<Long, Integer> trTypesUsage;

		List<User> users=getAllUsers();

		double savingsSum;
		
		for (User user: users) {
			
			savingsSum=0D;
			
			UserData uData=new UserData();
			uData.setUser( user);
			
			List<Trip> trips=getAllTripsSortedByTransportType( user);
			if (trips==null)
				return null;
			
			trTypesUsage=
				new HashMap<Long, Integer>();
			
			for (Trip trip: trips) {
				
				TransportType type = trip.getChosenCO2Option().getTransportType();
				
				if ( trTypesUsage.keySet().contains(type.getId())) {
					int count = trTypesUsage.get(type.getId());
					count++;
					trTypesUsage.remove(type.getId());
					trTypesUsage.put(type.getId(), count);
				}else
					trTypesUsage.put(type.getId(), 1);
			}
			
			savingsSum+= trips.get(0).getMaxCO2Option().getRate()
				- trips.get(0).getChosenCO2Option().getRate();
			for (int i=1; i<trips.size(); i++) {
				
				savingsSum+= trips.get(i).getMaxCO2Option().getRate()
					- trips.get(i).getChosenCO2Option().getRate();
			}
			
			uData.setSavingsSum( savingsSum);
			uData.setTransportTypes( trTypesUsage);
			usersData.add( uData);
		}
	
		return usersData;
	}
	
	private TransportType getTransportTypeById(int avgValue) {
		
		TransportType type=null;
		
		for (TransportType t: getTransportTypes()) {
			if (t.getId()==avgValue) {
				type=t;
				break;
			}
		}
		
		return type;
	}

	public HashMap<String, Double> calcUsersCosts() {
		
		HashMap<String, Double> usersCosts=
			new HashMap<String, Double>();
		
		List<User> users=getAllUsers();
		
		if (users==null)
			return null;
		
		for (User user: users)	{
			
			List<Trip> trips=getTripsByYear(null, user);
			
			if (trips==null)
				continue;
			
			double distanceSum=0D;
			double emissionRatesSum=0D;
			double cost=0D;
			
			for (Trip trip: trips) {
				distanceSum+=trip.getDistance()/1000;
				emissionRatesSum+=trip.getChosenCO2Option()
					.getRate();
				
				cost=distanceSum/emissionRatesSum;
				usersCosts.put( user.getName(), cost);
			}	
		}
			
		return usersCosts;
	}

	private List<User> getAllUsers() {
		return objService.query(User.class).list();
	}	
}